using System;
using System.Data;
using System.IO;
using System.Web.Services;
using ODX.Core;

#if !PocketPC
using System.Configuration;
#endif

namespace ODX.Core
{
    /// <summary>
    /// Lightweight XML data provider to save data into XML files.
    /// </summary>
    public class XmlDataProvider : MarshalByRefObject, IDataProvider
    {
        private DataSet ds;
        private string filename;

        /// <summary>
        /// Creates an in-memory XML provider.
        /// </summary>
        public XmlDataProvider() : this(null){}

        /// <summary>
        /// Creates XML data provider.
        /// </summary>
        /// <param name="filename">Path to the file to be used as a dsta source.</param>
        public XmlDataProvider(string filename)
        {
            lock (typeof(XmlDataProvider))
            {
                this.filename = filename;
                ds = new DataSet();
            }
        }

        private void RefreshDS()
        {
            lock (typeof(XmlDataProvider))
            {
                if (filename != null && File.Exists(filename))
                {
                    ds = new DataSet();
                    ds.ReadXml(filename);
                    ds.AcceptChanges();
                }
            }
        }

        private static void CopyDataRows(DataTable dstTable, int count, params DataRow[] srcRows)
        {
            lock (typeof (XmlDataProvider))
            {
                int i = 0;
                foreach (DataRow src in srcRows)
                {
                    DataRow dst = dstTable.NewRow();
                    foreach (DataColumn dc in dst.Table.Columns)
                        dst[dc.ColumnName] = src[dc.ColumnName];
                    dst.Table.Rows.Add(dst);
                    if (++i == count)
                        break;
                }
            }
        }

        /// <summary>
        /// Selects records from a data source using specified filter sort order.
        /// </summary>
        /// <param name="tableName">Table name in data source to be queried.</param>
        /// <param name="filter">Filter expression to be applied to the table. '?' marks are accepted as parameters placeholders</param>
        /// <param name="sort">List of sort fields.</param>
        /// <param name="count">Nubler of records to be queried.</param>
        /// <param name="parameters">Parameters to be used instead '?' in filter expression.</param>
        /// <returns>DataSet contained queried data and optionally prefetched records.</returns>
        public DataSet Select(string tableName, string filter, string sort, int count, params object[] parameters)
        {
            lock (typeof (XmlDataProvider))
            {
                RefreshDS();

                if ( ! ds.Tables.Contains(tableName) )
                    return new DataSet();

                if (parameters != null & filter != null)
                    filter =
                        FilterParser.Parse(filter,
                                           delegate(int n) { return DataSetValueFormatter.FormatValue(parameters[n]); });

                DataSet copy = ds.Clone();
                copy.EnforceConstraints = false;
                CopyDataRows(copy.Tables[tableName], count, ds.Tables[tableName].Select(filter, sort));
                copy.AcceptChanges();
                return copy;
            }
        }

        /// <summary>
        /// Queries single record from a data source.
        /// </summary>
        /// <param name="tableName">Table name in data source to be queried.</param>
        /// <param name="ID">ID of a record to be returned.</param>
        /// <returns>DataSet contained the requested record and optionally prefetched records.</returns>
        public DataSet GetRecord(string tableName, string ID)
        {
            lock (typeof (XmlDataProvider))
            {
                RefreshDS();

                DataSet copy = ds.Clone();
                copy.EnforceConstraints = false;
                DataRow src = ds.Tables[tableName].Rows.Find(ID);
                if (src != null)
                    CopyDataRows(copy.Tables[tableName], 0, src);

                copy.AcceptChanges();
                return copy;
            }
        }

        /// <summary>
        /// Returns complete list of child records for the specified parent record.
        /// </summary>
        /// <param name="childTableName">Name of child table.</param>
        /// <param name="childFK">Name of FK field in child table.</param>
        /// <param name="parentID">ID of parent record.</param>
        /// <returns>DataSet contained child records and optionally prefetched records.</returns>
        public DataSet GetChildren(string childTableName, string childFK, string parentID)
        {
            lock (typeof(XmlDataProvider))
            {
                RefreshDS();

                DataSet copy = ds.Clone();
                copy.EnforceConstraints = false;

                if ( ds.Tables.Contains(childTableName) )
                    CopyDataRows(copy.Tables[childTableName], 0,
                        ds.Tables[childTableName].Select(string.Format("{0} = '{1}'",
                            childFK, parentID)));

                copy.AcceptChanges();
                return copy;
            }
        }

        /// <summary>
        /// Returns records linked N:M to the specified parent record.
        /// </summary>
        /// <param name="linkTableName">Link table name.</param>
        /// <param name="childTableName">Name of table to query child records</param>
        /// <param name="parentFK">Name of fielf in link table that is FK to the parent table.</param>
        /// <param name="childFK">Name of fielf in link table that is FK to the child table.</param>
        /// <param name="parentID">ID of parent record.</param>
        /// <returns>DataSet contained child records and optionally prefetched records</returns>
        public DataSet GetAssoc(string linkTableName, string childTableName, string parentFK, string childFK, string parentID)
        {
            RefreshDS();

            DataSet copy = ds.Clone();
            copy.EnforceConstraints = false;

            if (ds.Tables.Contains(linkTableName) && ds.Tables.Contains(childTableName))
            {
                foreach (DataRow dr in ds.Tables[linkTableName].Select(string.Format("{0} = '{1}'", parentFK, parentID)))
                {
                    CopyDataRows(copy.Tables[linkTableName], 0, dr);
                    DataRow srcRec = ds.Tables[childTableName].Rows.Find(dr[childFK]);
                    if (srcRec != null)
                        CopyDataRows(copy.Tables[childTableName], 0, srcRec);
                }
            }

            copy.AcceptChanges();
            return copy;
        }

        /// <summary>
        /// Saves DataSet to the underlying data source
        /// </summary>
        /// <param name="changes">DataSet, typically contained changes made by user or algorithms.</param>
        /// <param name="saveMode">Concurrent changes processing rules.</param>
        public void Save(DataSet changes, SaveMode saveMode)
        {
            lock (typeof (XmlDataProvider))
            {
                RefreshDS();

                if (saveMode != SaveMode.Concurrent)
                    ds.Merge(changes, false, MissingSchemaAction.AddWithKey);
                else
                    SmartDataSetMerger.Merge(ds, changes, MissingSchemaAction.AddWithKey);

                ds.AcceptChanges();
                if (filename != null)
                    ds.WriteXml(filename, XmlWriteMode.WriteSchema);
            }
        }

        /// <summary>
        /// Defines positions of nulls in ordered data. 
        /// </summary>
        public NullsPosition NullsPosition
        {
            get { return NullsPosition.Minimum; }
        }
    }
}
